﻿using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using ClipperLib;
using RPGOnlineWPF.Extensions;
using RPGOnlineWPF.Renderer;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using TriangleNet.Geometry;
using Behavior = TriangleNet.Behavior;
using Device = SharpDX.Direct3D10.Device;
using Mesh = TriangleNet.Mesh;
using Resource = SharpDX.Direct3D10.Resource;

namespace RPGOnlineWPF.Model.MapEditor
{
	public class Wall : BaseInteractive<D3D10>
	{
		private D3D10 m_renderer;
		private Effect m_simpleEffect;
		private InputLayout m_vertexLayout;
		private Buffer m_vertices;
		private int m_trianglesCount;
		private Buffer m_indices;
		private Texture2D m_wallTexture;
		private ShaderResourceView m_wallTextureResource;
		private List<Vector4> m_linePositions;

		private bool m_rebuildBuffers = true;
		private Mesh m_lastComputedWall;

		const float m_scaleFactor = 1000.0f;

		public Wall()
		{
			m_linePositions = new List<Vector4>()
			{
				new Vector4( 6, 3, 0, 1), 
				new Vector4(12, 3, 0, 1),
				new Vector4(12,12, 0, 1),
				new Vector4( 6,12, 0, 1),
				//new Vector4( 6, 3, 0, 1)
			};
		}

		public IEnumerable<Vector4> Points
		{
			get { return m_linePositions; }
			set
			{
				m_linePositions.Clear();
				m_rebuildBuffers = true;

				if (value == null)
					return;
				m_linePositions.AddRange(value);
			}
		}

		public override void RenderScene(DrawEventArgs args)
		{
			Device device = m_renderer.Device;
			if (device == null)
				return;

			BuildWall();

			device.InputAssembler.InputLayout = m_vertexLayout;
			device.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
			device.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(m_vertices, Marshal.SizeOf(typeof(Vector4)), 0));
			device.InputAssembler.SetIndexBuffer(m_indices, Format.R32_UInt, 0);

			var color = m_simpleEffect.GetVariableBySemantic("Color").AsVector();
			color.Set(Color4.White);

			var modelViewProjection = m_renderer.Camera.View * m_renderer.Camera.Projection;
			var matrix = m_simpleEffect.GetVariableBySemantic("ModelViewProjection").AsMatrix();
			matrix.SetMatrix(modelViewProjection);

			var texture = m_simpleEffect.GetVariableBySemantic("Texture").AsShaderResource();
			texture.SetResource(m_wallTextureResource);

			EffectTechnique technique = m_simpleEffect.GetTechniqueByIndex(0);
			for (int i = 0; i < technique.Description.PassCount; ++i)
			{
				EffectPass pass = technique.GetPassByIndex(0);
				pass.Apply();
				device.DrawIndexed(m_trianglesCount * 3, 0, 0);
			}
		}

		public override void Attach()
		{
			Device device = m_renderer.Device;
			if (device == null)
				return;

			var compilationResult = ShaderBytecode.CompileFromFile("Resources\\Wall.fx", "fx_4_0");
			if (compilationResult.HasErrors)
				MessageBox.Show(compilationResult.Message);

			ShaderBytecode shaderBytes = compilationResult;
			m_simpleEffect = new Effect(device, shaderBytes);

			m_wallTexture = Resource.FromFile<Texture2D>(m_renderer.Device, "Resources\\Stone_04_UV_H_CM_1.jpg");
			m_wallTextureResource = new ShaderResourceView(m_renderer.Device, m_wallTexture);

			EffectTechnique technique = m_simpleEffect.GetTechniqueByIndex(0);
			EffectPass pass = technique.GetPassByIndex(0);

			m_vertexLayout = new InputLayout(device, pass.Description.Signature, new[] {
                new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0)
            });
		}

		private void BuildWall()
		{
			Device device = m_renderer.Device;
			if (device == null || !m_rebuildBuffers)
				return;

			m_lastComputedWall = MakeWall(m_linePositions, 0.5f);

			if (m_trianglesCount != m_lastComputedWall.Triangles.Count)
			{
				if (m_vertices != null) m_vertices.Dispose();
				if (m_indices != null) m_indices.Dispose();

				m_vertices = new Buffer(device, new BufferDescription()
				{
					BindFlags = BindFlags.VertexBuffer,
					CpuAccessFlags = CpuAccessFlags.Write,
					OptionFlags = ResourceOptionFlags.None,
					SizeInBytes = m_lastComputedWall.Triangles.Count * Marshal.SizeOf(typeof(Vector4)),
					Usage = ResourceUsage.Dynamic
				});

				m_indices = new Buffer(device, new BufferDescription()
				{
					BindFlags = BindFlags.IndexBuffer,
					CpuAccessFlags = CpuAccessFlags.Write,
					OptionFlags = ResourceOptionFlags.None,
					SizeInBytes = m_lastComputedWall.Triangles.Count * Marshal.SizeOf(typeof(int)) * 3,
					Usage = ResourceUsage.Dynamic
				});
			}

			m_trianglesCount = m_lastComputedWall.Triangles.Count;

			using (var vertexStream = m_vertices.Map(MapMode.WriteDiscard))
			{
				vertexStream.WriteRange(m_lastComputedWall.Vertices.Select(v => new Vector4((float) v.X, (float) v.Y, 0, 1)).ToArray());
				vertexStream.Position = 0;
				m_vertices.Unmap();
			}

			using (var indexStream = m_indices.Map(MapMode.WriteDiscard))
			{
				indexStream.WriteRange(m_lastComputedWall.Triangles.SelectMany(t => new[] {t.P0, t.P1, t.P2}).ToArray());
				indexStream.Position = 0;
				m_indices.Unmap();
			}

			m_rebuildBuffers = false;
		}

		private TriangleNet.Mesh MakeWall(IEnumerable<Vector4> linePositions, float width)
		{
			var originalPolygon = linePositions.Select(p => new IntPoint(p.X*m_scaleFactor, p.Y*m_scaleFactor)).ToList();
			var originalPolygons = Clipper.SimplifyPolygon(originalPolygon);

			var outsideOffsetPaths = Clipper.OffsetPaths(originalPolygons, width * m_scaleFactor, JoinType.jtMiter, EndType.etClosed, 2);
			var insideOffsetPaths = Clipper.OffsetPaths(originalPolygons, -width * m_scaleFactor, JoinType.jtMiter, EndType.etClosed, 2);
			insideOffsetPaths.ForEach(p => p.Reverse());

			InputGeometry inputGeometry = new InputGeometry();
			AddNodesToGeometry(outsideOffsetPaths, inputGeometry);
			AddNodesToGeometry(insideOffsetPaths, inputGeometry);
			
			TriangleNet.Mesh mesh = new TriangleNet.Mesh(new Behavior());
			mesh.Triangulate(inputGeometry);
			return mesh;
		}

		private void AddNodesToGeometry(List<List<IntPoint>> paths, InputGeometry inputGeometry)
		{
			foreach (var path in paths)
			{
				AddPathToGeometry(path, inputGeometry);

				if (!Clipper.Orientation(path))
				{
					InputGeometry tempGeometry = new InputGeometry();
					AddPathToGeometry(path, tempGeometry);
					TriangleNet.Mesh mesh = new TriangleNet.Mesh(new Behavior());
					mesh.Triangulate(tempGeometry);

					var firstTriangle = mesh.Triangles.First();
					var v1 = firstTriangle.GetVertex(0);
					var v2 = firstTriangle.GetVertex(1);
					var v3 = firstTriangle.GetVertex(2);

					double x = (v1.X + v2.X + v3.X) / 3;
					double y = (v1.Y + v2.Y + v3.Y) / 3;
					
					inputGeometry.AddHole(x, y);
				}
			}
		}

		private void AddPathToGeometry(List<IntPoint> path, InputGeometry inputGeometry)
		{
			int firstIndex = inputGeometry.Count;
			float x = 0, y = 0;
			int pCount = 0;
			foreach (var point in path)
			{
				float realX = point.X / m_scaleFactor;
				float realY = point.Y / m_scaleFactor;

				inputGeometry.AddPoint(realX, realY);
			}

			for (int i = 1; i < path.Count; i++)
			{
				inputGeometry.AddSegment(firstIndex + i - 1, firstIndex + i);
			}

			inputGeometry.AddSegment(firstIndex + path.Count - 1, firstIndex);
		}

		public override void Detach()
		{
			m_vertices.Dispose();
			m_indices.Dispose();
			m_vertexLayout.Dispose();
			m_simpleEffect.Dispose();
			m_wallTextureResource.Dispose();
			m_wallTexture.Dispose();
		}

		public override D3D10 Renderer
		{
			get { return m_renderer; }
			set { m_renderer = value; }
		}

		public override void OnMouseUp(UIElement ui, MouseButtonEventArgs e)
		{
			if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Released)
			{
				var mousePosition = m_renderer.Camera.GetVector(ui, e);
				if (!Selected && m_lastComputedWall.IsMouseOver(mousePosition))
				{
					Selected = true;
					e.Handled = true;
				}
			}

			base.OnMouseUp(ui, e);
		}

		public override void Reset(DrawEventArgs args)
		{
		}

		public override void Render(DrawEventArgs args)
		{
		}

		public void ChangePoint(int pointToChange, Vector4 newPosition)
		{
			m_linePositions[pointToChange] = newPosition;
			m_rebuildBuffers = true;
		}

		public int PointsCount
		{
			get { return m_linePositions.Count; }
		}
	}
}